﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DimensionsHelper.Common.Utils;

namespace DimensionsHelper.Common.Metadata.Script;

/// <summary>
///     分类值，脚本中使用花括号包裹（{}），如果从字符串转换，可以不要两侧的花括号。
/// </summary>
/// <remarks>
///     <list type="number">
///         <item>
///             <term>{ }</term>
///             <description>Gives an empty list.</description>
///         </item>
///         <item>
///             <term>{ x1, }</term>
///             <description>
///                 Considered as two single-category ranges. The second range is considered to be missing and is
///                 ignored. x1 is handled in the normal way.
///             </description>
///         </item>
///         <item>
///             <term>{^x1, }</term>
///             <description>Always gives an empty list, because the second range is inclusive and evaluates to nothing.</description>
///         </item>
///         <item>
///             <term>{^x1, ^}</term>
///             <description>The second range is ignored and x1 is excluded from the entire category list.</description>
///         </item>
///     </list>
/// </remarks>
public class Categorical : ExpressionBasicType, IEnumerable<CategoryValue>
{
    private readonly List<CategoryValue> _values;
    private readonly HashSet<object> _valueSet;

    public Categorical() : base(BasicTypes.Categorical)
    {
        _values = [];
        _valueSet = [];
    }

    public Categorical(Categorical categorical) : base(BasicTypes.Categorical)
    {
        _values = categorical._values.ToList();
        _valueSet = [.. categorical._valueSet];
    }

    public Categorical(params CategoryValue[] values) : this()
    {
        foreach (CategoryValue val in values)
        {
            Add(val.Value);
        }
    }

    public Categorical(params object[] values) : this(values.ToList())
    {
    }

    /// <summary>
    ///     从给定列表中创建分类值。
    /// </summary>
    /// <param name="values"></param>
    public Categorical(IEnumerable<object> values) : this()
    {
        foreach (object item in values)
        {
            switch (item)
            {
                case string str:
                    str = str.Trim();
                    if (string.IsNullOrEmpty(str))
                    {
                        continue;
                    }

                    if (int.TryParse(str, out int integer))
                    {
                        Add(integer);
                    }
                    else
                    {
                        Add(str);
                    }

                    break;

                case int intValue:
                    Add(intValue);
                    break;

                case CategoryValue cat:
                    _values.Add(cat);
                    break;

                default:
                    throw new Exception($"'{item}'不是有效的分类元素。");
            }
        }
    }

    /// <summary>
    ///     从字符串创建分类列表。
    /// </summary>
    /// <param name="text">给定字符串，可以忽略开头和结尾的花括号。</param>
    public Categorical(string text) : this()
    {
        if (text.StartsWith("{") && text.EndsWith("}"))
        {
            text = text.Substring(1, text.Length - 2);
        }

        foreach (string item in text.Split(','))
        {
            string catText = item.Trim();

            if (string.IsNullOrEmpty(catText))
            {
                continue;
            }

            if (int.TryParse(catText, out int integer))
            {
                Add(integer);
            }
            else
            {
                Add(catText);
            }
        }
    }

    /// <summary>
    ///     分类值的元素数量。
    /// </summary>
    public int Count => _values.Count;

    public CategoryValue this[int index] => _values[index];

    /// <summary>
    ///     尝试从字符串解析分类值，开头和结尾的花括号可以忽略，但是不能只有一个。
    /// </summary>
    /// <param name="text">给定字符串</param>
    /// <param name="categorical">解析后的分类值</param>
    /// <returns>如果成功转换，返回true，否则，返回false。</returns>
    public static bool TryParse(string text, out Categorical categorical)
    {
        categorical = new Categorical();

        if (text.StartsWith("{") && text.EndsWith("}"))
        {
            text = text.Substring(1, text.Length - 2);
        }

        foreach (string item in text.Split(','))
        {
            string catText = item.Trim();

            // 跳过空分类元素。
            if (string.IsNullOrEmpty(catText))
            {
                continue;
            }

            if (int.TryParse(catText, out int integer))
            {
                categorical.Add(integer);
            }
            else if (StringHelper.IsValidIdentifier(catText))
            {
                categorical.Add(catText);
            }
            else
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    ///     判断两个集合是否相等。
    /// </summary>
    /// <param name="categorical"></param>
    /// <returns></returns>
    public bool IsEqualTo(Categorical categorical)
    {
        Categorical intersection = Intersection(categorical);
        return intersection.Count == Count && intersection.Count == categorical.Count;
    }

    /// <summary>
    ///     判断给定集合是否是当前集合的子集。
    /// </summary>
    /// <param name="categorical"></param>
    /// <returns></returns>
    public bool IsSubset(Categorical categorical)
    {
        Categorical intersection = Intersection(categorical);
        return intersection.Count == categorical.Count;
    }

    /// <summary>
    ///     向当前列表中添加新的数值。
    /// </summary>
    /// <param name="value"></param>
    /// <remarks>
    ///     <list type="number">
    ///         <item>新添加的数值如果已经存在，将不做任何操作。</item>
    ///         <item>新添加的数值按顺序插入，同时更新区间的上下限。</item>
    ///     </list>
    /// </remarks>
    public void Add(int value)
    {
        // 和边界相同时，不修改集合。
        if (Contains(value))
        {
            return;
        }

        var obj = new CategoryValue();
        obj.SetValue(value);
        _values.Add(obj);
        _valueSet.Add(value);
    }

    /// <summary>
    ///     向集合末尾追加指定分类名称。
    /// </summary>
    /// <param name="name">分类名称，不区分大小写</param>
    public void Add(string name)
    {
        if (int.TryParse(name, out int integer))
        {
            Add(integer);
            return;
        }

        // 判断是否是有效的标识符
        if (!StringHelper.IsValidIdentifier(name))
        {
            throw ThrowHelper.StringCannotConvertTo(name, nameof(Categorical));
        }

        var obj = new CategoryValue();
        obj.SetValue(name);
        _values.Add(obj);
        _valueSet.Add(name.ToLower());
    }

    /// <summary>
    ///     向当前集合中添加整数或字符串类型的分类元素。
    /// </summary>
    /// <param name="value"></param>
    /// <exception cref="InvalidOperationException"></exception>
    public void Add(object value)
    {
        switch (value)
        {
            case int integer:
                Add(integer);
                break;

            case string str:
                Add(str);
                break;

            case CategoryValue cat:
                Add(cat.Value);
                break;

            case Categorical categorical:
                foreach (CategoryValue item in categorical)
                {
                    Add(item.Value);
                }

                break;

            default:
                throw new InvalidOperationException();
        }
    }


    /// <summary>
    ///     移除指定整数的成员值。
    /// </summary>
    /// <param name="number">指定整数</param>
    /// <returns>如果给定整数在当前集合中，返回true，否则，返回false。</returns>
    public bool Remove(int number)
    {
        if (_valueSet.Remove(number))
        {
            _values.RemoveAll(v => v.Equals(number));
            return true;
        }

        return false;
    }


    /// <summary>
    ///     移除指定名称的成员值。
    /// </summary>
    /// <param name="name">指定名称</param>
    /// <returns>如果给定名称在当前集合中，返回true，否则，返回false。</returns>
    public bool Remove(string name)
    {
        string lower = name.ToLower();
        if (_valueSet.Remove(lower))
        {
            _values.RemoveAll(v => v.Equals(lower));
            return true;
        }

        return false;
    }


    /// <summary>
    ///     移除指定成员值。
    /// </summary>
    /// <param name="value">指定成员值</param>
    /// <returns>如果给定成员值在当前集合中，返回true，否则，返回false。</returns>
    public bool Remove(CategoryValue value)
    {
        if (value.Value is string str)
        {
            return Remove(str);
        }

        if (value.Value is int iVal)
        {
            return Remove(iVal);
        }

        return false;
    }


    /// <summary>
    ///     移除给定分类集合中的所有值，如果给定集合的元素都不在当前集合中，返回false。
    /// </summary>
    /// <param name="cat">需要删除的分类值列表</param>
    /// <returns>如果成功删除了任意一个，将返回true。</returns>
    public bool Remove(Categorical cat)
    {
        bool res = false;

        foreach (CategoryValue item in cat)
        {
            if (Remove(item))
            {
                res = true;
            }
        }

        return res;
    }


    /// <summary>
    ///     判断给定数值是否在分类列表中。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public bool Contains(int value)
    {
        return _valueSet.Contains(value);
    }

    /// <summary>
    ///     判断给定名称是否在当前分类值列表中。
    /// </summary>
    /// <param name="name">给定分类名，不区分大小写。</param>
    /// <returns></returns>
    public bool Contains(string name)
    {
        return _valueSet.Contains(name.ToLower());
    }

    /// <summary>
    ///     判断当前集合是否包含给定集合中的任意一个。
    /// </summary>
    /// <param name="cat">给定集合</param>
    /// <param name="exactly">是否要求当前集合不包含给定集合以外的任意其他元素</param>
    /// <returns></returns>
    public bool Contains(Categorical cat, bool exactly = false)
    {
        Categorical intersection = Intersection(cat);
        return (!exactly || intersection.Count == Count) && intersection.Count > 0;
    }

    /// <summary>
    ///     判断当前集合是否包含给定集合中的一些，如果上限和下限无效，此方法将等同于<see cref="Contains(Categorical, bool)" />
    /// </summary>
    /// <param name="cat">给定集合</param>
    /// <param name="lowerBound">判定的交集数量下限，大于等于0才有效</param>
    /// <param name="upperBound">判定的交集数量上限，大于等于0才有效</param>
    /// <param name="exactly">是否要求当前集合不包含给定集合以外的任意其他元素</param>
    /// <returns></returns>
    public bool Contains(Categorical cat, int lowerBound, int upperBound, bool exactly = true)
    {
        Categorical intersection = Intersection(cat);
        int interCount = intersection.Count;
        bool ex = !exactly || interCount == Count;
        return ex &&
               (lowerBound >= 0 ? interCount >= lowerBound : interCount > 0) &&
               (upperBound >= 0 ? interCount <= upperBound : interCount > 0);
    }

    /// <summary>
    ///     判断给定的分类列表中的元素是否都包含在当前集合中。
    /// </summary>
    /// <param name="cat">给定分类列表</param>
    /// <param name="exactly">是否要求两个列表包含的元素相同</param>
    /// <returns></returns>
    public bool ContainsAll(Categorical cat, bool exactly = false)
    {
        Categorical intersection = Intersection(cat);
        return (!exactly || intersection.Count == Count) && intersection.Count == cat.Count;
    }

    /// <summary>
    ///     判断给定对象是否在当前列表中。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public bool Contains(object value)
    {
        switch (value)
        {
            case int integer:
                return Contains(integer);
            case string str:
                return Contains(str);
            case CategoryValue cat:
                return Contains(cat.Value);
            default:
                throw new InvalidOperationException();
        }
    }

    /// <summary>
    ///     在当前列表中查找指定值。
    /// </summary>
    /// <param name="value">给定字符串或者整数</param>
    /// <param name="skip">跳过的元素个数</param>
    /// <param name="reverse">是否从后往前查找</param>
    /// <returns>查找到的索引，未找到时返回-1。</returns>
    public int Find(object value, int skip = 0, bool reverse = false)
    {
        if (reverse)
        {
            for (int i = _values.Count - 1 - skip; i <= 0; i++)
            {
                if (_values[i].Equals(value))
                {
                    return i;
                }
            }
        }
        else
        {
            for (int i = skip; i < _values.Count; i++)
            {
                if (_values[i].Equals(value))
                {
                    return i;
                }
            }
        }

        return -1;
    }

    /// <summary>
    ///     去掉重复值后并返回一个新集合。
    /// </summary>
    /// <returns></returns>
    public Categorical Unique()
    {
        return new Categorical(_valueSet);
    }

    /// <summary>
    ///     获取和另一个分类列表的交集。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public Categorical Intersection(Categorical value)
    {
        var res = new Categorical();

        // 空集的交集也是空集。
        if (value.Count == 0 || Count == 0)
        {
            return res;
        }

        foreach (CategoryValue item in _values)
        {
            if (_valueSet.Contains(item.Value))
            {
                res._values.Add(new CategoryValue(item));
            }
        }

        return res;
    }

    /// <summary>
    ///     将表达式类型值转换为分类值并和当前分类值取交集。
    /// </summary>
    /// <param name="expression"></param>
    /// <returns></returns>
    public Categorical Intersection(ExpressionBasicType expression)
    {
        Categorical cate = (Categorical)expression.GetValue(BasicTypes.Categorical);
        return Intersection(cate);
    }

    /// <summary>
    ///     对<paramref name="value" />集合进行去同存异。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public Categorical Difference(Categorical value)
    {
        if (value.Count == 0)
        {
            return new Categorical(this);
        }

        if (Count == 0)
        {
            return new Categorical();
        }

        var res = new Categorical();

        foreach (CategoryValue item in _values)
        {
            if (!value.Contains(item.Value))
            {
                res.Add(item.Value);
            }
        }

        return res;
    }

    /// <summary>
    ///     取两个集合的交集。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public Categorical Union(Categorical value)
    {
        var res = new Categorical(this);

        foreach (CategoryValue item in value)
        {
            res.Add(item.Value);
        }

        return res;
    }

    /// <summary>
    ///     取两个集合的交集，<paramref name="expression" />会尝试转换成<see cref="Categorical" />类型。
    /// </summary>
    /// <param name="expression"></param>
    /// <returns></returns>
    public Categorical Union(ExpressionBasicType expression)
    {
        return Union((Categorical)expression.GetValue(BasicTypes.Categorical));
    }

    /// <summary>
    ///     取两个集合的异或集合。
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public Categorical XUnion(Categorical value)
    {
        var res = new Categorical();

        foreach (CategoryValue item in _values)
        {
            if (!value.Contains(item.Value))
            {
                res.Add(item.Value);
            }
        }

        foreach (CategoryValue item in value)
        {
            if (!_valueSet.Contains(item.Value))
            {
                res.Add(item.Value);
            }
        }

        return res;
    }

    /// <summary>
    ///     取两个集合的异或集合，<paramref name="expression" />会尝试转换成<see cref="Categorical" />类型。
    /// </summary>
    /// <param name="expression"></param>
    /// <returns></returns>
    public Categorical XUnion(ExpressionBasicType expression)
    {
        return XUnion((Categorical)expression.GetValue(BasicTypes.Categorical));
    }

    public override string ToString()
    {
        return "{" + string.Join(", ", _values.Select(e => e.Value.ToString())) + "}";
    }

    #region ExpressionBasicType

    public override object GetValue(BasicTypes type)
    {
        switch (type)
        {
            case BasicTypes.String:
                return ToString();
            case BasicTypes.Categorical:
                return this;
            case BasicTypes.Boolean:
                return Count > 0;
            default:
                throw new InvalidOperationException();
        }
    }

    public override ExpressionBasicType ConvertTo(BasicTypes type)
    {
        switch (type)
        {
            case BasicTypes.String:
                return new ExpressionString(ToString());
            case BasicTypes.Categorical:
                return this;
            case BasicTypes.Boolean:
                return new ExpressionBoolean(Count > 0);
            default:
                throw ThrowHelper.TypeConvertFailed("Categorical", type.ToString());
        }
    }

    public override bool CanImplicitlyConvert(BasicTypes type)
    {
        return type == BasicTypes.Categorical;
    }

    public override ExpressionBasicType Add(ExpressionBasicType expression)
    {
        return Union((Categorical)expression.ConvertTo(BasicTypes.Categorical));
    }

    public override ExpressionBasicType Subtract(ExpressionBasicType expression)
    {
        return Difference((Categorical)expression.ConvertTo(BasicTypes.Categorical));
    }

    public override ExpressionBasicType Multiply(ExpressionBasicType expression)
    {
        return Intersection((Categorical)expression.ConvertTo(BasicTypes.Categorical));
    }

    public override ExpressionBasicType Divide(ExpressionBasicType expression)
    {
        return XUnion((Categorical)expression.ConvertTo(BasicTypes.Categorical));
    }

    public override ExpressionBasicType Modulo(ExpressionBasicType expression)
    {
        throw ThrowHelper.UnsupportedOperatorOfTypes("%", "Categorical", "Categorical");
    }

    /*
     * Equal To
     *      The equal to (=) operator returns True if the two variables have the same categories irrespective of order.
     *
     *      | Examples                                             | Result        |
     *      |------------------------------------------------------|---------------|
     *      | {1,3,6,8} = {3,1,6,8}	                               | True          |
     *      | {1,3,6,8} = {3,1,6}	                               | False         |
     *      | {FOSSILS, BIRDS, WHALES} = {WHALES, BIRDS, FOSSILS}  | True          |
     *      | {FOSSILS, BIRDS, WHALES} = {BIRDS, FOSSILS}	       | False         |
     *
     * Not Equal To
     *      The equal to (<>) operator returns True if the two variables contain different categories.
     *
     *      | Examples                                             | Result       |
     *      |------------------------------------------------------|--------------|
     *      | {1,3,6,8} <> {3,1,6,8}                               | False        |
     *      | {1,3,6,8} <> {3,1,6}                                 | True         |
     *      | {FOSSILS, BIRDS, WHALES} <> {WHALES, BIRDS, FOSSILS} | False        |
     *      | {FOSSILS, BIRDS, WHALES} <> {BIRDS, FOSSILS}         | True         |
     *
     * Less Than and Less Than or Equal To
     *      The less than (<) operator returns True if the list of categories on the left side is a proper subset of the
     *      list of categories on the right side. The less than or equal to (<=) operator returns True if the list of categories
     *      on the left is a subset of, or equal to, the one on the right.
     *
     *      | Examples                                              | Result      |
     *      |-------------------------------------------------------|-------------|
     *      | {1,3,6} < {1,3,6,8}                                   | True        |
     *      | {1,3,6} < {1,3,8,9}                                   | False       |
     *      | {1,3,6} < {1,3,6}                                     | False       |
     *      | {1,3,6} <= {1,3,6}                                    | True        |
     *      | {FOSSILS, BIRDS, WHALES} < {WHALES, BIRDS, FOSSILS}   | False       |
     *      | {FOSSILS, BIRDS, WHALES} <= {WHALES, BIRDS, FOSSILS}  | True        |
     *      | {BIRDS, FOSSILS} < {FOSSILS, BIRDS, WHALES}           | True        |
     *
     * Greater Than and Greater Than or Equal To
     *      The greater than (>) operator returns True if the list of categories on the left side is a proper superset of the
     *      list of categories on the right side. The greater than or equal to (>=) operator returns True if the list of categories
     *      on the left is a superset of, or is equal to, the one on the right.
     *
     *      | Examples                                             | Result       |
     *      |------------------------------------------------------|--------------|
     *      | {1,3,6,8} > {1,3,6}                                  | True         |
     *      | {1,3,8,9} > {1,3,6}                                  | False        |
     *      | {1,3,6} > {1,3,6}                                    | False        |
     *      | {1,3,6} >= {1,3,6}                                   | True         |
     *      | {FOSSILS, BIRDS, WHALES} > {WHALES, BIRDS, FOSSILS}  | False        |
     *      | {FOSSILS, BIRDS, WHALES} >= {WHALES, BIRDS, FOSSILS} | True         |
     *      | {WHALES, BIRDS, FOSSILS} > {FOSSILS, BIRDS}          | True         |
     *
     * Has Intersection
     *      The Has Intersection (=*) operator returns true if the intersection of the two values is not empty.
     *
     *      | Examples                                | Result            |
     *      |-----------------------------------------|-------------------|
     *      | {1,4,5} =* {3,1,6,8}                    | True              |
     *      | {3, 8} =* {1,6}                         | False             |
     *      | {FOSSILS, BIRDS } =* {WHALES, FOSSILS}  | True              |
     *      | { WHALES} =* {BIRDS, FOSSILS}           | False             |
     *
     *
     */

    public override ExpressionBasicType IsEqualTo(ExpressionBasicType expression)
    {
        return new ExpressionBoolean(IsEqualTo((Categorical)expression.ConvertTo(BasicTypes.Categorical)));
    }

    public override ExpressionBasicType IsGreaterThan(ExpressionBasicType expression)
    {
        Categorical rightCat = (Categorical)expression.ConvertTo(BasicTypes.Categorical);
        Categorical intersection = Intersection(rightCat);
        return new ExpressionBoolean(intersection.Count == rightCat.Count && intersection.Count < Count);
    }

    public override ExpressionBasicType IsGreaterThanEqual(ExpressionBasicType expression)
    {
        Categorical rightCat = (Categorical)expression.ConvertTo(BasicTypes.Categorical);
        Categorical intersection = Intersection(rightCat);
        return new ExpressionBoolean(intersection.Count == rightCat.Count && intersection.Count <= Count);
    }

    public override ExpressionBasicType IsLessThan(ExpressionBasicType expression)
    {
        Categorical rightCat = (Categorical)expression.ConvertTo(BasicTypes.Categorical);
        Categorical intersection = Intersection(rightCat);
        return new ExpressionBoolean(intersection.Count == Count && intersection.Count < rightCat.Count);
    }

    public override ExpressionBasicType IsLessThanEqual(ExpressionBasicType expression)
    {
        Categorical rightCat = (Categorical)expression.ConvertTo(BasicTypes.Categorical);
        Categorical intersection = Intersection(rightCat);
        return new ExpressionBoolean(intersection.Count == Count && intersection.Count <= rightCat.Count);
    }

    #endregion

    #region IEnumerable

    public IEnumerator<CategoryValue> GetEnumerator()
    {
        return _values.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}